package com.googlecode.lohr.tools.interpreter;

import java.io.File;
import java.io.IOException;

import lohr.lang.dom.CompilationUnit;
import lohr.lang.dom.IdentifierName;

import com.googlecode.lingwah.ParseContext;
import com.googlecode.lingwah.ParseResults;
import com.googlecode.lohr.tools.language.LohrDOMBuilder;
import com.googlecode.lohr.tools.language.LohrGrammar;
import com.googlecode.lohr.utils.IOUtils;
import com.googlecode.lohr.utils.Logging;

/**
 * An <a href="http://en.wikipedia.org/wiki/Interpreter_(computing)">interpreter</a> for the lohr language.
 * @author ted.stockwell
 */
public class LohrInterpreter {
	
	LohrRepository _respository= new LohrRepository(); 
	
	
	TypeImpl TypeType= new TypeImpl(this, "lohr.lang.Class") {
		public TypeImpl getType() {
			return this;
		};
	};
	TypeImpl PackageType= new TypeImpl(this, "lohr.lang.Package") {
		public TypeImpl getType() {
			return this;
		};
	};
	TypeImpl CompilationUnitType= new TypeImpl(this, "lohr.lang.CompilationUnit") {
		public TypeImpl getType() {
			return this;
		};
	};
	TypeImpl ClosureType= new TypeImpl(this, "lohr.lang.Closure") {
		public TypeImpl getType() {
			return this;
		};
	};
	TypeImpl MethodType= new TypeImpl(this, "lohr.lang.Method") {
		public TypeImpl getType() {
			return this;
		};
	};

	/**
	 * Object that represents TRUE
	 */
	BooleanLiteralImpl TRUE= new BooleanLiteralImpl(this);
	/**
	 * Object that represents FALSE
	 */
	BooleanLiteralImpl FALSE= new BooleanLiteralImpl(this);
	
	private final FunctionInvocationImpl _main;
	private final String[] _args;
	
	
	/**
	 * @param classPath - tells lohr where to look in the filesystem for files.  
	 * Similar to Java classpath.
	 * 
	 * @param mainFunction - the URI of a function that accepts an array of string.
	 * Similar to Java main method 
	 */
	public LohrInterpreter(File[] classPath, String mainFunction, String[] args) {
		initBasicResources();
		_args= args;
		
		for (File path : classPath) {
			processClasspathFolder("", path);
		}
		Object obj= _respository.findByURI(mainFunction);
		if (obj == null)
			throw new RuntimeException(mainFunction+" not found");
		if (!(obj instanceof FunctionInvocationImpl))
			throw new RuntimeException(mainFunction+" does not refer to a function");
		_main= (FunctionInvocationImpl)obj;
	}
	
	public void start() {
		_main.invoke(_args);
	}

	
	private void initBasicResources() {
		_respository.register(TypeType);
		_respository.register(PackageType);
		_respository.register(CompilationUnitType);
		_respository.register(ClosureType);
	}


	/**
	 * This method loads all source modules in all packages in/under the given folder.
	 */
	synchronized void processClasspathFolder(String packageName, File folder) {
		if (!folder.exists()) {
			Logging.info("Path does not exist: "+folder);
			return;
		}
		
		boolean loadPackage= false;
		synchronized (this) {
			Object packag= _respository.findByURI(packageName);
			if (packag == null) { // this package has not been loaded yet
				_respository.register(new PackageImpl(this, packageName));
				loadPackage= true;
			}
		}
		if (loadPackage) {
			File[] files= folder.listFiles();
			for (File file : files) {
				if (file.isDirectory()) {
					processClasspathFolder(packageName+"."+file.getName(), file);
				}
				else if (file.getName().endsWith(".lohr")) {
					loadModule(packageName, file);
				}
			}
		}
	}


	/**
	 * Loads a source module and executes it.
	 * Executing a source module causes the elements declared in the module 
	 * to be registered in the system registry.
	 */
	void loadModule(String packageName, File file) {
		try {
			String text= new String(IOUtils.readFully(file));
			
			ParseContext context= new ParseContext(text);
			ParseResults results= context.getParseResults(LohrGrammar.DEFINITION.CompilationUnit, 0);
			if (!results.success())
				throw results.getError();
			int length= results.longestLength();
			if (length < text.length())
				throw results.getError();
			
			CompilationUnit compilationUnit= (CompilationUnit)LohrDOMBuilder.process(results);
			new CompilationUnitImpl(this, compilationUnit).invoke();
		} catch (IOException e) {
			throw new RuntimeException(e);
		} 
	}

	public LohrRepository getRespository() {
		return _respository;
	}

	/**
	 * Return an identifier that is the fully qualified equivalent of the given identifier.
	 */
	public IdentifierName resolve(IdentifierName name) {
		return name;
	}
}
